﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace laohan.Common.USB.Cryptology
{
	public class TripleDESCryptoProvider
	{
		public abstract class DESCrypto
		{
			protected readonly byte[] InputPermutation = new byte[64]
			{
			58,
			50,
			42,
			34,
			26,
			18,
			10,
			2,
			60,
			52,
			44,
			36,
			28,
			20,
			12,
			4,
			62,
			54,
			46,
			38,
			30,
			22,
			14,
			6,
			64,
			56,
			48,
			40,
			32,
			24,
			16,
			8,
			57,
			49,
			41,
			33,
			25,
			17,
			9,
			1,
			59,
			51,
			43,
			35,
			27,
			19,
			11,
			3,
			61,
			53,
			45,
			37,
			29,
			21,
			13,
			5,
			63,
			55,
			47,
			39,
			31,
			23,
			15,
			7
			};

			protected readonly byte[] FinalPermutation = new byte[64]
			{
			40,
			8,
			48,
			16,
			56,
			24,
			64,
			32,
			39,
			7,
			47,
			15,
			55,
			23,
			63,
			31,
			38,
			6,
			46,
			14,
			54,
			22,
			62,
			30,
			37,
			5,
			45,
			13,
			53,
			21,
			61,
			29,
			36,
			4,
			44,
			12,
			52,
			20,
			60,
			28,
			35,
			3,
			43,
			11,
			51,
			19,
			59,
			27,
			34,
			2,
			42,
			10,
			50,
			18,
			58,
			26,
			33,
			1,
			41,
			9,
			49,
			17,
			57,
			25
			};

			protected readonly byte[] ExpansionPermutation = new byte[48]
			{
			32,
			1,
			2,
			3,
			4,
			5,
			4,
			5,
			6,
			7,
			8,
			9,
			8,
			9,
			10,
			11,
			12,
			13,
			12,
			13,
			14,
			15,
			16,
			17,
			16,
			17,
			18,
			19,
			20,
			21,
			20,
			21,
			22,
			23,
			24,
			25,
			24,
			25,
			26,
			27,
			28,
			29,
			28,
			29,
			30,
			31,
			32,
			1
			};

			protected readonly byte[][] SubstitutionBoxes = new byte[8][]
			{
			new byte[64]
			{
				14,
				4,
				13,
				1,
				2,
				15,
				11,
				8,
				3,
				10,
				6,
				12,
				5,
				9,
				0,
				7,
				0,
				15,
				7,
				4,
				14,
				2,
				13,
				1,
				10,
				6,
				12,
				11,
				9,
				5,
				3,
				8,
				4,
				1,
				14,
				8,
				13,
				6,
				2,
				11,
				15,
				12,
				9,
				7,
				3,
				10,
				5,
				0,
				15,
				12,
				8,
				2,
				4,
				9,
				1,
				7,
				5,
				11,
				3,
				14,
				10,
				0,
				6,
				13
			},
			new byte[64]
			{
				15,
				1,
				8,
				14,
				6,
				11,
				3,
				4,
				9,
				7,
				2,
				13,
				12,
				0,
				5,
				10,
				3,
				13,
				4,
				7,
				15,
				2,
				8,
				14,
				12,
				0,
				1,
				10,
				6,
				9,
				11,
				5,
				0,
				14,
				7,
				11,
				10,
				4,
				13,
				1,
				5,
				8,
				12,
				6,
				9,
				3,
				2,
				15,
				13,
				8,
				10,
				1,
				3,
				15,
				4,
				2,
				11,
				6,
				7,
				12,
				0,
				5,
				14,
				9
			},
			new byte[64]
			{
				10,
				0,
				9,
				14,
				6,
				3,
				15,
				5,
				1,
				13,
				12,
				7,
				11,
				4,
				2,
				8,
				13,
				7,
				0,
				9,
				3,
				4,
				6,
				10,
				2,
				8,
				5,
				14,
				12,
				11,
				15,
				1,
				13,
				6,
				4,
				9,
				8,
				15,
				3,
				0,
				11,
				1,
				2,
				12,
				5,
				10,
				14,
				7,
				1,
				10,
				13,
				0,
				6,
				9,
				8,
				7,
				4,
				15,
				14,
				3,
				11,
				5,
				2,
				12
			},
			new byte[64]
			{
				7,
				13,
				14,
				3,
				0,
				6,
				9,
				10,
				1,
				2,
				8,
				5,
				11,
				12,
				4,
				15,
				13,
				8,
				11,
				5,
				6,
				15,
				0,
				3,
				4,
				7,
				2,
				12,
				1,
				10,
				14,
				9,
				10,
				6,
				9,
				0,
				12,
				11,
				7,
				13,
				15,
				1,
				3,
				14,
				5,
				2,
				8,
				4,
				3,
				15,
				0,
				6,
				10,
				1,
				13,
				8,
				9,
				4,
				5,
				11,
				12,
				7,
				2,
				14
			},
			new byte[64]
			{
				2,
				12,
				4,
				1,
				7,
				10,
				11,
				6,
				8,
				5,
				3,
				15,
				13,
				0,
				14,
				9,
				14,
				11,
				2,
				12,
				4,
				7,
				13,
				1,
				5,
				0,
				15,
				10,
				3,
				9,
				8,
				6,
				4,
				2,
				1,
				11,
				10,
				13,
				7,
				8,
				15,
				9,
				12,
				5,
				6,
				3,
				0,
				14,
				11,
				8,
				12,
				7,
				1,
				14,
				2,
				13,
				6,
				15,
				0,
				9,
				10,
				4,
				5,
				3
			},
			new byte[64]
			{
				12,
				1,
				10,
				15,
				9,
				2,
				6,
				8,
				0,
				13,
				3,
				4,
				14,
				7,
				5,
				11,
				10,
				15,
				4,
				2,
				7,
				12,
				9,
				5,
				6,
				1,
				13,
				14,
				0,
				11,
				3,
				8,
				9,
				14,
				15,
				5,
				2,
				8,
				12,
				3,
				7,
				0,
				4,
				10,
				1,
				13,
				11,
				6,
				4,
				3,
				2,
				12,
				9,
				5,
				15,
				10,
				11,
				14,
				1,
				7,
				6,
				0,
				8,
				13
			},
			new byte[64]
			{
				4,
				11,
				2,
				14,
				15,
				0,
				8,
				13,
				3,
				12,
				9,
				7,
				5,
				10,
				6,
				1,
				13,
				0,
				11,
				7,
				4,
				9,
				1,
				10,
				14,
				3,
				5,
				12,
				2,
				15,
				8,
				6,
				1,
				4,
				11,
				13,
				12,
				3,
				7,
				14,
				10,
				15,
				6,
				8,
				0,
				5,
				9,
				2,
				6,
				11,
				13,
				8,
				1,
				4,
				10,
				7,
				9,
				5,
				0,
				15,
				14,
				2,
				3,
				12
			},
			new byte[64]
			{
				13,
				2,
				8,
				4,
				6,
				15,
				11,
				1,
				10,
				9,
				3,
				14,
				5,
				0,
				12,
				7,
				1,
				15,
				13,
				8,
				10,
				3,
				7,
				4,
				12,
				5,
				6,
				11,
				0,
				14,
				9,
				2,
				7,
				11,
				4,
				1,
				9,
				12,
				14,
				2,
				0,
				6,
				10,
				13,
				15,
				3,
				5,
				8,
				2,
				1,
				14,
				7,
				4,
				10,
				8,
				13,
				15,
				12,
				9,
				0,
				3,
				5,
				6,
				11
			}
			};

			protected readonly byte[] Permutation = new byte[32]
			{
			16,
			7,
			20,
			21,
			29,
			12,
			28,
			17,
			1,
			15,
			23,
			26,
			5,
			18,
			31,
			10,
			2,
			8,
			24,
			14,
			32,
			27,
			3,
			9,
			19,
			13,
			30,
			6,
			22,
			11,
			4,
			25
			};

			protected readonly byte[] PC1Permutation = new byte[56]
			{
			57,
			49,
			41,
			33,
			25,
			17,
			9,
			1,
			58,
			50,
			42,
			34,
			26,
			18,
			10,
			2,
			59,
			51,
			43,
			35,
			27,
			19,
			11,
			3,
			60,
			52,
			44,
			36,
			63,
			55,
			47,
			39,
			31,
			23,
			15,
			7,
			62,
			54,
			46,
			38,
			30,
			22,
			14,
			6,
			61,
			53,
			45,
			37,
			29,
			21,
			13,
			5,
			28,
			20,
			12,
			4
			};

			protected readonly byte[] PC2Permutation = new byte[48]
			{
			14,
			17,
			11,
			24,
			1,
			5,
			3,
			28,
			15,
			6,
			21,
			10,
			23,
			19,
			12,
			4,
			26,
			8,
			16,
			7,
			27,
			20,
			13,
			2,
			41,
			52,
			31,
			37,
			47,
			55,
			30,
			40,
			51,
			45,
			33,
			48,
			44,
			49,
			39,
			56,
			34,
			53,
			46,
			42,
			50,
			36,
			29,
			32
			};

			protected readonly byte[] Rotations = new byte[16]
			{
			1,
			1,
			2,
			2,
			2,
			2,
			2,
			2,
			1,
			2,
			2,
			2,
			2,
			2,
			2,
			1
			};

			protected byte[] Substitution6x4(byte[] input)
			{
				input = SplitBytes(input, 6);
				byte[] array = new byte[input.Length / 2];
				int num = 0;
				for (int i = 0; i < input.Length; i++)
				{
					byte b = input[i];
					int num2 = 2 * ((b >> 7) & 1) + ((b >> 2) & 1);
					int num3 = (b >> 3) & 0xF;
					int num4 = SubstitutionBoxes[i][16 * num2 + num3];
					if (i % 2 == 0)
					{
						num = num4;
					}
					else
					{
						array[i / 2] = (byte)(16 * num + num4);
					}
				}
				return array;
			}

			protected byte[] SplitBytes(byte[] input, int length)
			{
				int num = (8 * input.Length - 1) / length + 1;
				byte[] array = new byte[num];
				for (int i = 0; i < num; i++)
				{
					for (int j = 0; j < length; j++)
					{
						int bit = GetBit(input, length * i + j);
						SetBit(array, 8 * i + j, bit);
					}
				}
				return array;
			}

			protected byte[][] GenerateSubkeys(byte[] key)
			{
				int num = PC1Permutation.Length;
				int num2 = Rotations.Length;
				byte[] input = SelectBits(key, PC1Permutation);
				int num3 = num / 2;
				byte[] array = SelectBits(input, 0, num3);
				byte[] array2 = SelectBits(input, num3, num3);
				byte[][] array3 = new byte[num2][];
				for (int i = 0; i < num2; i++)
				{
					array = RotateLeft(array, num3, Rotations[i]);
					array2 = RotateLeft(array2, num3, Rotations[i]);
					byte[] input2 = ConcatenateBits(array, num3, array2, num3);
					array3[i] = SelectBits(input2, PC2Permutation);
				}
				return array3;
			}

			protected byte[] RotateLeft(byte[] input, int length, int step)
			{
				byte[] array = new byte[(length - 1) / 8 + 1];
				for (int i = 0; i < length; i++)
				{
					int bit = GetBit(input, (i + step) % length);
					SetBit(array, i, bit);
				}
				return array;
			}

			protected byte[] ConcatenateBits(byte[] a, int aLength, byte[] b, int bLength)
			{
				byte[] array = new byte[(aLength + bLength - 1) / 8 + 1];
				int num = 0;
				for (int i = 0; i < aLength; i++)
				{
					int bit = GetBit(a, i);
					SetBit(array, num, bit);
					num++;
				}
				for (int j = 0; j < bLength; j++)
				{
					int bit2 = GetBit(b, j);
					SetBit(array, num, bit2);
					num++;
				}
				return array;
			}

			protected byte[] XORBytes(byte[] a, byte[] b)
			{
				byte[] array = new byte[a.Length];
				for (int i = 0; i < a.Length; i++)
				{
					array[i] = (byte)(a[i] ^ b[i]);
				}
				return array;
			}

			protected byte[] SelectBits(byte[] input, int position, int length)
			{
				byte[] array = new byte[(length - 1) / 8 + 1];
				for (int i = 0; i < length; i++)
				{
					int bit = GetBit(input, position + i);
					SetBit(array, i, bit);
				}
				return array;
			}

			protected byte[] SelectBits(byte[] input, byte[] map)
			{
				byte[] array = new byte[(map.Length - 1) / 8 + 1];
				for (int i = 0; i < map.Length; i++)
				{
					int bit = GetBit(input, map[i] - 1);
					SetBit(array, i, bit);
				}
				return array;
			}

			protected int GetBit(byte[] data, int position)
			{
				int num = position / 8;
				int num2 = position % 8;
				return (data[num] >> 8 - (num2 + 1)) & 1;
			}

			protected void SetBit(byte[] data, int position, int value)
			{
				int num = position / 8;
				int num2 = position % 8;
				byte b = data[num];
				b = (byte)((uint)((65407 >> num2) & b) & 0xFFu);
				data[num] = (byte)((value << 8 - (num2 + 1)) | b);
			}
		}

		public class DESCryptoDecoder : DESCrypto, IDecryptable
		{
			string IDecryptable.Decode(string data, string key, bool isParallel)
			{
				return Encoding.Unicode.GetString(((IDecryptable)this).Decode(Encoding.Unicode.GetBytes(data), key, isParallel));
			}

			byte[] IDecryptable.Decode(byte[] data, string key, bool isParallel)
			{
				return ((IDecryptable)this).Decode(data, Encoding.Unicode.GetBytes(key), isParallel);
			}

			byte[] IDecryptable.Decode(byte[] data, byte[] key, bool isParallel)
			{
				byte[][] subkeys = GenerateSubkeys(key);
				byte[] result = new byte[data.Length];
				byte[] block = new byte[8];
				if (isParallel)
				{
					Parallel.For(0, data.Length / 8, delegate (int i)
					{
						Array.Copy(data, 8 * i, block, 0, 8);
						Array.Copy(DecodeBlock(block, subkeys), 0, result, 8 * i, 8);
					});
				}
				else
				{
					for (int j = 0; j < data.Length / 8; j++)
					{
						Array.Copy(data, 8 * j, block, 0, 8);
						Array.Copy(DecodeBlock(block, subkeys), 0, result, 8 * j, 8);
					}
				}
				return result;
			}

			private byte[] DecodeBlock(byte[] data, byte[][] subkeys)
			{
				byte[] input = SelectBits(data, InputPermutation);
				int num = InputPermutation.Length;
				byte[] array = SelectBits(input, 0, num / 2);
				byte[] array2 = SelectBits(input, num / 2, num / 2);
				for (int i = 0; i < subkeys.Length; i++)
				{
					byte[] array3 = array2;
					array2 = SelectBits(array2, ExpansionPermutation);
					array2 = XORBytes(array2, subkeys[subkeys.Length - i - 1]);
					array2 = Substitution6x4(array2);
					array2 = SelectBits(array2, Permutation);
					array2 = XORBytes(array, array2);
					array = array3;
				}
				byte[] input2 = ConcatenateBits(array2, num / 2, array, num / 2);
				return SelectBits(input2, FinalPermutation);
			}
		}

		public class DESCryptoEncoder : DESCrypto, IEncryptable
		{
			string IEncryptable.Encode(string data, string key, bool isParallel)
			{
				return Encoding.Unicode.GetString(((IEncryptable)this).Encode(Encoding.Unicode.GetBytes(data), key, isParallel));
			}

			byte[] IEncryptable.Encode(byte[] data, string key, bool isParallel)
			{
				return ((IEncryptable)this).Encode(data, Encoding.Unicode.GetBytes(key), isParallel);
			}

			byte[] IEncryptable.Encode(byte[] data, byte[] key, bool isParallel)
			{
				byte[][] subkeys = GenerateSubkeys(key);
				byte[] result = new byte[data.Length];
				byte[] block = new byte[8];
				if (isParallel)
				{
					Parallel.For(0, data.Length / 8, delegate (int i)
					{
						Array.Copy(data, 8 * i, block, 0, 8);
						Array.Copy(EncodeBlock(block, subkeys), 0, result, 8 * i, 8);
					});
				}
				else
				{
					for (int j = 0; j < data.Length / 8; j++)
					{
						Array.Copy(data, 8 * j, block, 0, 8);
						Array.Copy(EncodeBlock(block, subkeys), 0, result, 8 * j, 8);
					}
				}
				return result;
			}

			private byte[] EncodeBlock(byte[] block, byte[][] subkeys)
			{
				byte[] input = SelectBits(block, InputPermutation);
				int num = InputPermutation.Length;
				byte[] array = SelectBits(input, 0, num / 2);
				byte[] array2 = SelectBits(input, num / 2, num / 2);
				for (int i = 0; i < subkeys.Length; i++)
				{
					byte[] array3 = array2;
					array2 = SelectBits(array2, ExpansionPermutation);
					array2 = XORBytes(array2, subkeys[i]);
					array2 = Substitution6x4(array2);
					array2 = SelectBits(array2, Permutation);
					array2 = XORBytes(array, array2);
					array = array3;
				}
				byte[] input2 = ConcatenateBits(array2, num / 2, array, num / 2);
				return SelectBits(input2, FinalPermutation);
			}
		}

		public interface IDecryptable
		{
			string Decode(string data, string key, bool isParallel = false);

			byte[] Decode(byte[] data, string key, bool isParallel = false);

			byte[] Decode(byte[] data, byte[] key, bool isParallel = false);
		}

		public interface IEncryptable
		{
			string Encode(string data, string key, bool isParallel = false);

			byte[] Encode(byte[] data, string key, bool isParallel = false);

			byte[] Encode(byte[] data, byte[] key, bool isParallel = false);
		}

		public byte[] TripleDESEncrypt(byte[] data_in, byte[] key_in)
		{
			byte[] array = new byte[8];
			byte[] array2 = new byte[8];
			byte[] array3 = new byte[8];
			byte[] array4 = new byte[8];
			byte[] array5 = new byte[8];
			byte[] array6 = new byte[40];
			IEncryptable encryptable = new DESCryptoEncoder();
			IDecryptable decryptable = new DESCryptoDecoder();
			for (byte b = 0; b < 16; b = (byte)(b + 1))
			{
				if (b < 8)
				{
					array[b] = key_in[b];
				}
				else
				{
					array2[b - 8] = key_in[b];
				}
			}
			for (byte b2 = 0; b2 < 5; b2 = (byte)(b2 + 1))
			{
				for (byte b = 0; b < 8; b = (byte)(b + 1))
				{
					array3[b] = data_in[8 * b2 + b];
				}
				array4 = encryptable.Encode(array3, array);
				array4 = decryptable.Decode(array4, array2);
				array5 = encryptable.Encode(array4, array);
				for (byte b = 0; b < 8; b = (byte)(b + 1))
				{
					array6[8 * b2 + b] = array5[b];
				}
			}
			return array6;
		}

		public byte[] TripleDESDecrypt(byte[] data_in, byte[] key_in)
		{
			byte[] array = new byte[8];
			byte[] array2 = new byte[8];
			byte[] array3 = new byte[8];
			byte[] array4 = new byte[8];
			byte[] array5 = new byte[8];
			byte[] array6 = new byte[40];
			IEncryptable encryptable = new DESCryptoEncoder();
			IDecryptable decryptable = new DESCryptoDecoder();
			for (byte b = 0; b < 16; b = (byte)(b + 1))
			{
				if (b < 8)
				{
					array[b] = key_in[b];
				}
				else
				{
					array2[b - 8] = key_in[b];
				}
			}
			for (byte b2 = 0; b2 < 5; b2 = (byte)(b2 + 1))
			{
				for (byte b = 0; b < 8; b = (byte)(b + 1))
				{
					array3[b] = data_in[8 * b2 + b];
				}
				array4 = decryptable.Decode(array3, array);
				array4 = encryptable.Encode(array4, array2);
				array5 = decryptable.Decode(array4, array);
				for (byte b = 0; b < 8; b = (byte)(b + 1))
				{
					array6[8 * b2 + b] = array5[b];
				}
			}
			return array6;
		}
	}
}
